/**
 * huize Service Inc
 * All Rights Reserved @2018
 */
package com.huize.zhike.framework.common.util;

import java.util.concurrent.atomic.AtomicLong;

/**
 * 描述:
 *
 * @author tianyuliang
 * @version $Id: SequenceUtils.java, v0.1 2020/8/20
 */
public class SequenceUtils {

    private static final long SERVER_IDENTIFIER = 255;

    /**
     * The last time value. Used to remove duplicate IDs.
     */
    private final static AtomicLong lastTime = new AtomicLong(Long.MIN_VALUE);

    /**
     * generate distributed Id.
     *
     * @return
     */
    public static String getDefaultDistributedId() {
        return getDistributedId(SERVER_IDENTIFIER);
    }


    /**
     * generate distributed ID.
     *
     * @param serverIdentifier support 256 servers. 0-255
     * @return
     */
    public static String getDistributedId(long serverIdentifier) {
        int length = 16;
        return getDistributedId(serverIdentifier, length);
    }

    /**
     * generate distributed ID.
     *
     * @param serverIdentifier support 256 servers. 0-255
     * @return
     */
    public static String getDistributedId(long serverIdentifier, int length) {
        long identifier = serverIdentifier << 56;
        long distributedId = identifier | genTimeBasedId();
        String id = Long.toHexString(distributedId);
        while (id.length() < length) {
            id = "0" + id;
        }
        return id;
    }

    /**
     * Generates a new time field. Each time field is unique and larger than the
     * previously generated time field.
     *
     * @return
     */
    private static long genTimeBasedId() {
        return genTimeBasedId(System.currentTimeMillis());
    }

    /**
     * generate a new time field from the given timestamp. Note that even identical
     * values of <code>time</code> will produce different time fields.
     *
     * @param time
     * @return
     */
    private static long genTimeBasedId(long time) {

        // 6 bits for clk_seq
        time = time << 6;

        // try again if compareAndSet failed
        while (true) {
            long tempTime = lastTime.get();
            if (time > tempTime) {
                if (lastTime.compareAndSet(tempTime, time)) {
                    break;
                }
            } else {
                // +1 to make sure the timestamp field is different
                //even the time is identical.
                if (lastTime.compareAndSet(tempTime, ++tempTime)) {
                    time = tempTime;
                    break;
                }
            }
        }
        return time;
    }


    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            System.out.println(getDefaultDistributedId());
        }
    }


}
